<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>Custom widget in PyGTK</title>
<link rel="stylesheet" href="/cfg/format.css" type="text/css">
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<meta name="keywords" content="Custom widget, Python, PyGTK, tutorial, programming">
<meta name="description" content="Custom widget in PyGTK">
<meta name="language" content="en">
<meta name="author" content="Jan Bodnar">
<meta name="distribution" content="global">

<script type="text/javascript" src="/lib/jquery.js"></script>
<script type="text/javascript" src="/lib/common.js"></script>

</head>

<body>

<div class="container">

<div id="wide_ad" class="ltow">
<script type="text/javascript"><!--
google_ad_client = "pub-9706709751191532";
/* 160x600, August 2011 */
google_ad_slot = "2484182563";
google_ad_width = 160;
google_ad_height = 600;
//-->
</script>
<script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js">
</script>
</div>

<div class="content">


<a href="/" title="Home">Home</a>&nbsp;
<a href="..">Contents</a>


<h1>Custom widget in PyGTK</h1>


<p>
Have you ever looked at an application and wondered, how a particular 
gui item was created? Probably every wannabe programmer has. Then you 
were looking at a list of widgets provided by your favourite gui library. 
But you couldn't find it. Toolkits usually provide only the most common 
widgets like buttons, text widgets, sliders etc. No toolkit can provide 
all possible widgets.
</p>

<script type="text/javascript"><!--
google_ad_client = "pub-9706709751191532";
/* NewSquare */
google_ad_slot = "0364418177";
google_ad_width = 300;
google_ad_height = 250;
//-->
</script> 
<script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js"> 
</script>

<p>
There are actually two kinds of toolkits. Spartan toolkits and heavy 
weight toolkits. The FLTK toolkit is a kind of a spartan toolkit. It 
provides only the very basic widgets and assumes, that the programemer 
will create the more complicated ones himself. wxWidgets is a heavy weight one.
It has lots of widgets. Yet it does not provide the more specialized widgets. 
For example a speed meter widget, a widget that measures the capacity of a 
CD to be burned (found e.g. in nero). Toolkits also don't have usually charts.
</p>
<p>
Programmers must create such widgets by themselves. They do it by using the 
drawing tools provided by the toolkit. 
There are two possibilities. A programmer can modify or enhance an existing widget.
Or he can create a custom widget from scratch. 
</p>

<h2>Burning widget</h2>

<p>
This is an example of a widget, that we create from scratch. This widget 
can be found in various media burning applications, like Nero Burning ROM.
</p>

<div class="codehead">burning.py</div>
<pre class="code">
#!/usr/bin/python
# -*- coding: utf-8 -*-

# ZetCode PyGTK tutorial 
#
# This example creates a burning
# custom widget
#
# author: Jan Bodnar
# website: zetcode.com 
# last edited: April 2011


import gtk
import cairo

class Burning(gtk.DrawingArea):

    def __init__(self, parent):
      
        self.par = parent
        super(Burning, self).__init__()
 
        self.num = ( "75", "150", "225", "300", 
            "375", "450", "525", "600", "675" )
 
        self.set_size_request(-1, 30)
        self.connect("expose-event", self.expose)
    

    def expose(self, widget, event):
      
        cr = widget.window.cairo_create()
        cr.set_line_width(0.8)

        cr.select_font_face("Courier", 
            cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL)
        cr.set_font_size(11)

        width = self.allocation.width
     
        self.cur_width = self.par.get_cur_value()

        step = round(width / 10.0)

        till = (width / 750.0) * self.cur_width
        full = (width / 750.0) * 700

        if (self.cur_width >= 700):
            
            cr.set_source_rgb(1.0, 1.0, 0.72)
            cr.rectangle(0, 0, full, 30)
            cr.save()
            cr.clip()
            cr.paint()
            cr.restore()
            
            cr.set_source_rgb(1.0, 0.68, 0.68)
            cr.rectangle(full, 0, till-full, 30)
            cr.save()
            cr.clip()
            cr.paint()
            cr.restore()

        else:     
            cr.set_source_rgb(1.0, 1.0, 0.72)
            cr.rectangle(0, 0, till, 30)
            cr.save()
            cr.clip()
            cr.paint()
            cr.restore()
       

        cr.set_source_rgb(0.35, 0.31, 0.24)
        
        for i in range(1, len(self.num) + 1):
            cr.move_to(i*step, 0)
            cr.line_to(i*step, 5)
            cr.stroke()
            
            (x, y, width, height, dx, dy) = cr.text_extents(self.num[i-1])
            cr.move_to(i*step-width/2, 15)
            cr.text_path(self.num[i-1])
            cr.stroke()
       
        
 
class PyApp(gtk.Window): 

    def __init__(self):
        super(PyApp, self).__init__()
        
        self.set_title("Burning")
        self.set_size_request(350, 200)        
        self.set_position(gtk.WIN_POS_CENTER)
        self.connect("destroy", gtk.main_quit)

        self.cur_value = 0
       
        vbox = gtk.VBox(False, 2)
        
        scale = gtk.HScale()
        scale.set_range(0, 750)
        scale.set_digits(0)
        scale.set_size_request(160, 40)
        scale.set_value(self.cur_value)
        scale.connect("value-changed", self.on_changed)
                
        fix = gtk.Fixed()
        fix.put(scale, 50, 50)
        
        vbox.pack_start(fix)
        
        self.burning = Burning(self)
        vbox.pack_start(self.burning, False, False, 0)

        self.add(vbox)
        self.show_all()
        
        
    def on_changed(self, widget):
        self.cur_value = widget.get_value()
        self.burning.queue_draw()
    
    
    def get_cur_value(self):
        return self.cur_value
    

PyApp()
gtk.main()
</pre>

<p>
We put a <b class="keyword">DrawingArea</b> on the bottom of the window 
and draw the entire widget manually. All the important code resides in 
the <b class="keyword">expose()</b> method of the Burning class. 
This widget shows graphically the total capacity of a medium and 
the free space available to us. The widget is controlled by a scale widget. 
The minimum value of our custom widget is 0, the maximum is 750. 
If we reach value 700, we began drawing in red colour. This normally indicates overburning.  
</p>

<pre class="explanation">
self.num = ( "75", "150", "225", "300", 
    "375", "450", "525", "600", "675" )
</pre>

<p>
These numbers are shown on the burning widget. They show the capacity of the medium.
</p>


<pre class="explanation">
self.cur_width = self.par.get_cur_value()
</pre>

<p>
These two lines get the current number from the scale widget. We get the 
parent widget and from the parent widget, we get the current value. 
</p>

<pre class="explanation">
till = (width / 750.0) * self.cur_width
full = (width / 750.0) * 700
</pre>

<p>
The till parameter determines the total size to be drawn. This value 
comes from the slider widget. It is a proportion of the whole area. 
The full parameter determines the point,
where we begin to draw in red color. 
</p>


<pre class="explanation">
cr.set_source_rgb(1.0, 1.0, 0.72)
cr.rectangle(0, 0, till, 30)
cr.save()
cr.clip()
cr.paint()
cr.restore()
</pre>

<p>
This code here, draws a yellow rectangle up to point, where the medium is full.
</p>

<pre class="explanation">
(x, y, width, height, dx, dy) = cr.text_extents(self.num[i-1])
cr.move_to(i*step-width/2, 15)
cr.text_path(self.num[i-1])
cr.stroke()
</pre>

<p>
This code here draws the numbers on the burning widget. We calculate the
<b class="keyword">TextExtents</b> to position the text correctly.
</p>

<pre class="explanation">
def on_changed(self, widget):
    self.cur_value = widget.get_value()
    self.burning.queue_draw()
</pre>

<p>
We get the value from the scale widget, store it in the <b class="keyword">cur_value</b> variable 
for later use. We redraw the burning widget. 
</p>

<br>
<img src="/img/gui/pygtk/burning.png" alt="Burning widget">
<div class="figure">Figure: Burning widget</div>

<hr class="btm">

<p>
In this chapter, we created a custom widget in PyGTK.
</p>

<br>
<div class="center"> 
<script type="text/javascript"><!--
google_ad_client = "pub-9706709751191532";
/* horizontal */
google_ad_slot = "1734478269";
google_ad_width = 468;
google_ad_height = 60;
//-->
</script> 
<script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js"> 
</script> 
</div> 
<br>



<div class="botNav, center">
<span class="botNavItem"><a href="/">Home</a></span> ‡ <span class="botNavItem"><a href="..">Contents</a></span> ‡
<span class="botNavItem"><a href="#">Top of Page</a></span>
</div>


<div class="footer">
<div class="signature">
<a href="/">ZetCode</a> last modified February 8, 2009  <span class="copyright">&copy; 2007 - 2012 Jan Bodnar</span>
</div>
</div>

</div> <!-- content -->

</div> <!-- container -->

</body>
</html>

